package unifor.mia.redes.relprotocol;

import unifor.mia.redes.relprotocol.channel.Channel;
import unifor.mia.redes.relprotocol.msg.Pacote;

public class ReliableReceiver extends ReliableProtocolComponent {

	private static long ordem = 0l;
	private static long ultimoConfirmado = -1;
   private CommunicationMonitor monitor;
	
	@Override
	public void run() {
      if(monitor == null){
         throw new RuntimeException("Monitor de comunicação não definido");
      }
		for(;;){
			String msg = receive();
			System.out.println("MENSAGEM RECEBIDA: " + msg);
		}

	}

   public void setCommunicationMonitor(CommunicationMonitor cm) {
      this.monitor = cm;
   }

   private String receive() {
		Pacote m;
		StringBuilder sb = new StringBuilder();
		boolean fim = false;
		
		while(!fim){
			m = Channel.read();
			if(m.getSeq() == ordem) { // se é um pacote com o seq da ordem esperada, consome
				if(checkIntegrity(m)){
					Channel.ACK(ordem);
					System.out.println("[Receiver] ACK com SEQ = " + ordem + " enviado pelo Receiver");
					sb.append(m.getBody());
					fim = m.end();
					ultimoConfirmado = ordem;
					++ordem; // próximo sequencial esperado
               monitor.packetReceived(m);
				} else {
               monitor.packetDiscardedChecksumError(m);
					System.out.println("[Receiver] Pacote com SEQ = " + m.getSeq() + " descartado por erro de checksum");
					Channel.ACK(ultimoConfirmado);
				}
			} else { // se o sequencial é diferente, eh pq a mensagem está fora de ordem. Descarta e manda ACK
            monitor.packetDiscardedOutOfOrder(m);
				System.out.println("[Receiver] Pacote com SEQ = " + m.getSeq() + " descartado por estar fora de ordem. Esperado: " + ordem);
				Channel.ACK(ultimoConfirmado);
			}
		}
		return sb.toString();
	}

	private boolean checkIntegrity(Pacote m) {
		String body = m.getBody();
		String hash = criarHash(body);
		return hash.equals(m.getHash());
	}

}
